import Data.List (repeat)

data Grid = Floor | EmptySeat | OccupiedSeat deriving (Eq,Show)

type Position = (Int,Int)
data Layout = Layout [[Grid]] (Int,Int) deriving (Show, Eq)

parseGrid :: Char -> Grid
parseGrid ch = case ch of
    '.' -> Floor
    'L' -> EmptySeat
    '#' -> OccupiedSeat

parseInput :: String -> Layout
parseInput string = 
    Layout grids (rowCount,colCount)
    where
        stringLines = lines string
        grids = map parseLine stringLines

        rowCount = length grids
        colCount = length . head $ grids

        parseLine :: String -> [Grid]
        parseLine = map parseGrid

-- getGrid :: Layout -> Position -> Grid
-- getGrid (Layout grids _) (row,col) = (grids !! row) !! col

getMaybeGrid :: Layout -> Position -> Maybe Grid
getMaybeGrid (Layout grids (rowCount,colCount)) (row,col) 
    | row < 0 || row >= rowCount = Nothing
    | col < 0 || col >= colCount = Nothing
    | otherwise =  Just $ (grids !! row) !! col

takeJust :: [Maybe a] -> [a]
takeJust [] = []
takeJust (x:xs) = case x of
    Just v -> v : takeJust xs
    Nothing -> takeJust xs

adjacentPositions :: Position -> [Position]
adjacentPositions (row,col) = [(row-1,col-1), (row-1,col  ), (row-1,col+1),
                               (row  ,col-1),                (row  ,col+1),
                               (row+1,col-1), (row+1,col  ), (row+1,col+1)]

obeyRule :: ([Grid] -> Bool) -> Layout -> Position -> Bool
obeyRule predicate layout pos = 
    predicate adjgrids
    where
        adjPoses = adjacentPositions pos
        adjgrids = takeJust . map (getMaybeGrid layout) $ adjPoses    

obeyEmptySeatRule :: Layout -> Position -> Bool
obeyEmptySeatRule = obeyRule (all (/=OccupiedSeat))

obeyOccupiedSeatRule :: Layout -> Position -> Bool
obeyOccupiedSeatRule = obeyRule ((>=4) . length . filter (==OccupiedSeat))

changeGrid :: Layout -> (Position,Grid) -> Grid
changeGrid layout (pos,grid) 
    | grid == Floor = Floor
    | grid == EmptySeat = if obeyEmptySeatRule layout pos then OccupiedSeat else EmptySeat
    | grid == OccupiedSeat = if obeyOccupiedSeatRule layout pos then EmptySeat else OccupiedSeat

applyRule :: Layout -> Layout
applyRule layout@(Layout grids (row,col)) = 
    Layout newGrids (row,col)
    where
        posgrids = map colFunc . zip [0..(row-1)] $ grids :: [[(Position,Grid)]]

        newGrids = map (map (changeGrid layout)) posgrids

        colFunc :: (Int,[Grid]) -> [(Position,Grid)]
        colFunc (r,gridline) = zip indecies gridline
            where
                indecies = zip (repeat r) [0..(col-1)] :: [Position]

loopApply :: Layout -> Layout
loopApply layout = 
    if newLayout == layout 
        then newLayout
        else loopApply newLayout
    where
        newLayout = applyRule layout

occupiedSeatCount :: Layout -> Int
occupiedSeatCount (Layout grids _) = 
    sum . map (length . filter (==OccupiedSeat)) $ grids

main = do
    input <- readFile "input.txt"
    print . occupiedSeatCount . loopApply . parseInput $ input
    